package meshToSolid;

import java.awt.geom.GeneralPath;
import java.io.File;
import java.util.Arrays;
import java.util.Vector;

import grid.Grid3DFloat;
import grid.Grid3DReached;
import hdgeom.bezier.Bezier;
import hdgeom.primitives.Box;
import mmesh.MMesh;
import solid.SimpleMCube;
import peasy.PeasyCam;
import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PImage;
import processing.core.PVector;
import simpleGeom.Polygon2D;
import simpleGui.*;

public class VoxelizationHighResColumn extends PApplet {
	String path = "/Users/dillenburger_b/Desktop/wetransfer-Column/";
	// String pathObject = path + "120acSolid.obj";
	String pathObject = path + "705gV2Accentuated.obj";

	String pathValue = pathObject.replace(".obj", ".txt");
	String pathMCubeOut = pathObject.replace(".obj", "vox.obj");
	boolean scanForm = true;
	float dimGrid = 0.001f;// size of voxels in meters

	MMesh mesh;
	Box boundsScan; // Boundingbox of objectfile
	// Box boundsInsideInt; // Boundingbox defining voxelized part
	// Box boundsCrop=new Box(-1.97f,-2.00f, 0.02f,0.02f,1.65f,2.9f);
	/*Box boundsCrop = new Box(-1.575f, -1.7534246f, 0.015989847f, 0.01f,
			1.4465753f, 2.14f);*/
	Box boundsCrop = new Box(-0.0f, -1.625f, -0.3533f, 0.3533f,
			1.725f, 0.3533f);

	SimpleMCube mc = new SimpleMCube();
	SimpleMCube mcPreview = new SimpleMCube();

	// PCamera3D cam;
	Grid3DFloat grid;
	int displayMode = 0;
	Gui gui;
	GuiSpinnerGroup sx1, sx2, sy1, sy2, sz1, sz2;
	Grid3DFloat gridPreview;

	boolean flagUpdateInsideOutside;
	boolean flagExport = false;

	// float iso=dimGrid*0.85f;
	float isoFactor = 0.85f;
	float iso = dimGrid * isoFactor;
	PeasyCam cam;

	float rY = 0;
	int step = 4;

	Polygon2D border;



	public void setup() {
		
		pathValue = path+"scl_rolo603.txt";
		size(1000, 600, P3D);
		setupGui();
		cam = new PeasyCam(this, 500);

		//constructScanBounds();
		
		// defineInsideOutside();
		//loadGrid(pathValue);
		// scanContinues("/Users/dillenburger_b/Documents/Projects/2014 Grotto2/hq/michael04/");

		voxelize(scanForm);
	}

	public void setupGui() {
		int py = 20;
		int pyO = 10;
		int x1 = 10;
		int x2 = 30;
		gui = new Gui(this, this.createFont("Arial", 12));
		gui.addGuiLabel(x1, py + pyO, "x1x2: ");
		sx1 = gui.addGuiSpinnerGroup(x2, py, boundsCrop.x1, -3f, 3f, 4, 3);
		py += 25;
		sx2 = gui.addGuiSpinnerGroup(x2, py, boundsCrop.x2, -3, 3f, 4, 3);
		py += 25;
		gui.addGuiLabel(x1, py + pyO, "y1: ");
		sy1 = gui.addGuiSpinnerGroup(x2, py, boundsCrop.y1, -3, 3, 4, 3);
		py += 25;
		gui.addGuiLabel(x1, py + pyO, "y2: ");
		sy2 = gui.addGuiSpinnerGroup(x2, py, boundsCrop.y2, -3, 3, 4, 3);
		py += 25;
		gui.addGuiLabel(x1, py + pyO, "z1: ");
		sz1 = gui.addGuiSpinnerGroup(x2, py, boundsCrop.z1, -1, 4, 4, 3);
		py += 25;
		gui.addGuiLabel(x1, py + pyO, "z2: ");
		sz2 = gui.addGuiSpinnerGroup(x2, py, boundsCrop.z2, -1, 4, 4, 3);

	}

	public void loadOBJ() {
		mesh = new MMesh();
		mesh.loadOBJ(pathObject);
		mesh.triangulateQuads();
		boundsScan=mesh.getBounds();
		boundsScan.offset(0.02f);
	}

	public void voxelize(boolean doScan) {
		if (doScan) {
			loadOBJ();
			scan(dimGrid);

			grid.saveValues(pathValue);
		} else {
			// loadOBJ();
		}

		defineInsideOutside();
		if (!flagExport) {
			mc.marchingCubes(grid.nX, grid.nY, grid.nZ, grid.values,
					grid.dim * 0.85f, grid.dim, grid.x1, grid.y1, grid.z1, null);
		} else {
			mc.marchingCubes(grid.nX, grid.nY, grid.nZ, grid.values,
					grid.dim * 0.85f, grid.dim, grid.x1, grid.y1, grid.z1, path
							+ timeStamp() + ".obj");
			flagExport = false;
		}
	}

	public void scan(float dim) {
		float dimX = boundsScan.getDimX();
		float dimY = boundsScan.getDimY();
		float dimZ = boundsScan.getDimZ();
		int nX = (int) (dimX / dim) + 2;
		int nY = (int) (dimY / dim) + 2;
		int nZ = (int) (dimZ / dim) + 2;
		println("nX: " + nX + "  nY: " + nY + "  nZ: " + nZ);
		grid = new Grid3DFloat(nX, nY, nZ);
		grid.x1 = boundsScan.x1;
		grid.y1 = boundsScan.y1;
		grid.z1 = boundsScan.z1;
		grid.dim = dim;
		// scanning
		MMesh.scanMeshDistance(mesh.xc, mesh.yc, mesh.zc, mesh.triangles,
				boundsScan.x1, boundsScan.y1, boundsScan.z1, nX, nY, nZ, dim,
				dim * 1.6f, grid.values);
	}

	public void constructScanBounds() {
		boundsScan = new Box();
		boundsScan.x1 = -0.17f;
		boundsScan.y1 = -2.04f;
		boundsScan.z1 = -0.2f;
		boundsScan.x2 = 0.17f;
		boundsScan.y2 = 2.04f;
		boundsScan.z2 = 0.2f;
		boundsScan.offset(dimGrid * 2f);
	}

	public Box constructIntegerBounds(Grid3DFloat grid) {
		return new Box(grid.getXInt(sx1.getFloatValue()), grid.getYInt(sy1
				.getFloatValue()), grid.getZInt(sz1.getFloatValue()),
				grid.getXInt(sx2.getFloatValue()) + 1, grid.getYInt(sy2
						.getFloatValue()) + 1,
				grid.getZInt(sz2.getFloatValue()) + 1);
	}

	public void loadGrid(String fileName) {
		grid = new Grid3DFloat(fileName);

	}

	

	
	public void initPreviewGrid() {

		gridPreview = new Grid3DFloat(grid.nX / step, grid.nY / step, grid.nZ
				/ step);
		gridPreview.x1 = grid.x1;
		gridPreview.y1 = grid.y1;
		gridPreview.z1 = grid.z1;
		gridPreview.dim = grid.dim * step;

	}

	public void loadPreviewGrid() {
		for (int x = 0; x < gridPreview.nX; x++) {
			for (int y = 0; y < gridPreview.nY; y++) {
				for (int z = 0; z < gridPreview.nZ; z++) {
					gridPreview.set(x, y, z,
							grid.get(x * step, y * step, z * step));
				}
			}
		}
		/*
		 * for (int x=0;x<gridPreview.nX;x++){ for (int
		 * y=0;y<gridPreview.nY;y++){ for (int z=0;z<gridPreview.nZ;z++){
		 * gridPreview.set(x,y,z,grid.get(x*step, y*step, z*step)); } } }
		 */
		Box boundsInsideInt = this.constructIntegerBounds(grid);
		for (int x = 0; x < grid.nX; x++) {
			for (int y = 0; y < grid.nY; y++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (boundsInsideInt.x1 > x || boundsInsideInt.x2 < x
							|| boundsInsideInt.y1 > y || boundsInsideInt.y2 < y
							|| boundsInsideInt.z1 > z || boundsInsideInt.z2 < z) {
						grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] = grid.dim * 2;
					}
				}
			}
		}

		mcPreview.marchingCubes(gridPreview.nX, gridPreview.nY, gridPreview.nZ,
				gridPreview.values, iso, gridPreview.dim, gridPreview.x1,
				gridPreview.y1, gridPreview.z1, null);
	}

	public void loadBoundingBox(String folderPath) {
		Box b = new Box();
		Vector<File> files = FileHelper.getFilesFromFolder(folderPath, ".obj");
		for (int i = 0; i < files.size(); i++) {
			File file = files.get(i);
			mesh = new MMesh();
			mesh.loadOBJ(file.getAbsolutePath());
			println("file:" + i + "/" + files.size() + " name"
					+ file.getAbsolutePath());
			mesh.triangulateQuads();
			if (i == 0) {
				b = mesh.getBounds();
			} else {
				b.add(mesh.getBounds());
			}
		}
		println(b);
	}

	
	public void defineInsideOutside() {

		float zPos = sz1.getFloatValue() + 0.01f;
		float yPos = sy1.getFloatValue() + 0.01f;
		float xPos = sx1.getFloatValue() + 0.01f;
		grid = new Grid3DFloat(pathValue);
		Box boundsInsideInt = constructIntegerBounds(grid);
		int iX = grid.getXInt(xPos);
		int iY = grid.getYInt(yPos);
		int iZ = grid.getZInt(zPos);
		println(" pos int: " + iX + " " + iY + " " + iZ);
		println("grid." + grid.nX + " " + grid.nY + " " + grid.nZ);
		int index = grid.getIndex(iX, iY, iZ);

		println("dim: " + grid.dim);

		println("grid pt1: " + grid.x1() + " " + grid.y1() + " " + grid.z1());
		println("grid pt2: " + grid.x2() + " " + grid.y2() + " " + grid.z2());
		initPreviewGrid();
		println(" grid dim: " + grid.x1 + " " + grid.y1 + " " + grid.z1);
		// println(" grid dim: ");
		for (int x = 0; x < grid.nX; x++) {
			for (int y = 0; y < grid.nY; y++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (boundsInsideInt.x1 > x || boundsInsideInt.x2 < x
							|| boundsInsideInt.y1 > y || boundsInsideInt.y2 < y
							|| boundsInsideInt.z1 > z || boundsInsideInt.z2 < z) {
						grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] = 0;
					} else {
						if (grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] < 0) {
							grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] *= -1;
						}
					}
				}
			}
		}

		println("start reach");
		println("grid info");

		grid.reached(index, iso);
		/*
		 * Grid3DFloat.reached(grid.values, grid.nX, grid.nY, grid.nZ, index,
		 * iso);
		 */
		println("end reach");
		// negative reached
		for (int i = 0; i < grid.values.length; i++) {
			if (grid.values[i] < 0)
				grid.values[i] *= -1;// reached
			else if (grid.values[i] > iso)
				grid.values[i] = 0;// unreached
		}

		for (int x = 0; x < grid.nX; x++) {
			for (int y = 0; y < grid.nY; y++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (boundsInsideInt.x1 > x || boundsInsideInt.x2 < x
							|| boundsInsideInt.y1 > y || boundsInsideInt.y2 < y
							|| boundsInsideInt.z1 > z || boundsInsideInt.z2 < z) {
						grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] = grid.dim * 2;
					}
				}
			}
		}
		// smaller iso->inside
		for (int x = 0; x < grid.nX; x++) {
			for (int y = 0; y < grid.nY; y++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (grid.get(x, y, z) < iso) {
						if (boundsInsideInt.x1 == x || boundsInsideInt.x2 == x
								|| boundsInsideInt.y1 == y
								|| boundsInsideInt.y2 == y
								|| boundsInsideInt.z1 == z
								|| boundsInsideInt.z2 == z) {
							grid.values[x * grid.nY * grid.nZ + y * grid.nZ + z] = 0;
						}
					}
				}
			}
		}
		crop(grid);
		loadPreviewGrid();
	}

	public void crop(Grid3DFloat grid) {
		// if (outsideCrop) distance to line
		// if (insideCrop and unreached)

		// smaller iso->inside
		float zM = 1.5f;
		border = new Polygon2D();
		border.addPt(sx2.getFloatValue(), sz2.getFloatValue());
		border.addPt(sx2.getFloatValue(), sz1.getFloatValue());
		border.addPt(sx1.getFloatValue(), sz1.getFloatValue());
		// border.addPt(sx1.getFloatValue(),zM);
		Bezier bez = new Bezier();
		bez.addControlPoint(sx1.getFloatValue(), zM, 0);
		bez.addControlPoint(sx1.getFloatValue(), zM + 1, 0);
		bez.addControlPoint(sx2.getFloatValue() - 1, sz2.getFloatValue(), 0);
		bez.addControlPoint(sx2.getFloatValue(), sz2.getFloatValue(), 0);
		int nSegs = 100;
		for (int i = 0; i < nSegs; i++) {
			PVector p = bez.getPoint(i * 1f / nSegs);
			border.addPt(p.x, p.y);
		}

		border = new Polygon2D();
		border.addPt(sx2.getFloatValue(), sz2.getFloatValue());
		border.addPt(sx2.getFloatValue(), sz1.getFloatValue());
		// border.addPt(sx1.getFloatValue(),sz1.getFloatValue());
		// border.addPt(sx1.getFloatValue(),zM);
		bez = new Bezier();
		bez.addControlPoint(sx1.getFloatValue(), sz1.getFloatValue(), 0);
		bez.addControlPoint(sx1.getFloatValue(), sz1.getFloatValue()+1, 0);
		bez.addControlPoint(sx1.getFloatValue(), sz1.getFloatValue()+1, 0);
		bez.addControlPoint(sx1.getFloatValue(), sz1.getFloatValue()+1.5f, 0);
		bez.addControlPoint(sx2.getFloatValue()-1, sz2.getFloatValue(), 0);
		//bez.addControlPoint(sx1.getFloatValue()-1,sz2.getFloatValue(),0);
		bez.addControlPoint(sx2.getFloatValue(), sz2.getFloatValue(), 0);
		nSegs = 100;
		for (int i = 0; i < nSegs; i++) {
			PVector p = bez.getPoint(i * 1f / nSegs);
			border.addPt(p.x, p.y);
		}
		GeneralPath area = border.getGeneralPath();

		float[][] inoutLayer = new float[grid.nX][grid.nZ];
		for (int x = 0; x < grid.nX; x++) {
			for (int z = 0; z < grid.nZ; z++) {
				float xP = grid.getXFloat(x);
				float yP = grid.getZFloat(z);
				float d = border.getClosestDist(xP, yP);
				if (!area.contains(xP, yP)) {
					inoutLayer[x][z] = border.getClosestDist(xP, yP);
				}
			}
		}

		// cropping everything that is outside
		for (int y = 0; y < grid.nY; y++) {
			for (int x = 0; x < grid.nX; x++) {
				for (int z = 0; z < grid.nZ; z++) {
					if (grid.get(x, y, z) < iso) {
						if (inoutLayer[x][z] > 0) {
							grid.set(x, y, z, inoutLayer[x][z]);
						}
					}
				}
			}
		}
	}
	
	
	
	

	public void draw() {

		Box boundsInsideInt = constructIntegerBounds(grid);
		if (flagUpdateInsideOutside) {
			flagUpdateInsideOutside = false;

			defineInsideOutside();
			mc.marchingCubes(grid.nX, grid.nY, grid.nZ, grid.values,
					grid.dim * 0.85f, grid.dim, grid.x1, grid.y1, grid.z1, null);
		}
		if (flagExport) {
			flagExport = false;
			mc.saveObj(path+timeStamp()+".obj");
			//saveSegments();
		}
		drawData();
		cam.beginHUD();
		noLights();
		drawGui();
		cam.endHUD();
	}

	public void drawData() {
		background(0);
		pushMatrix();
		// cam.apply((PGraphics) g);
		rotateY(PI + rY);
		hint(ENABLE_DEPTH_TEST);

		float dimX = grid.x2() - grid.x1();
		scale(3 / dimX * 50);
		scale(1, -1, 1);
		PCamera3D.drawWorldAxis3D(g, 5);
		pointLight(170, 170, 170, 3, 3, 3);
		pointLight(170, 170, 170, -3, -3, -3);
		if (displayMode == 0) {
			noFill();
			stroke(0, 255, 0);
			// mesh.draw((PGraphics3D) g);
			/*
			 * beginShape(TRIANGLES); for (int i = 0; i < mesh.triangles.length;
			 * i += 3) { for (int j = 0; j < 3; j++) {
			 * vertex(mesh.xc[mesh.triangles[i + j]], mesh.yc[mesh.triangles[i +
			 * j]], mesh.zc[mesh.triangles[i + j]]); } } endShape();
			 */
		}
		noStroke();
		fill(255);
		// mc.draw((PGraphics) g);
		mcPreview.draw(g);
		noFill();
		stroke(0, 0, 255);
		// the bounds
		Box boundsInsideInt = this.constructIntegerBounds(grid);
		translate(grid.x1, grid.y1, grid.z1);
		scale(grid.dim);
		translate(boundsInsideInt.getCenterX(), boundsInsideInt.getCenterY(),
				boundsInsideInt.getCenterZ());
		box(boundsInsideInt.getDimX(), boundsInsideInt.getDimY(),
				boundsInsideInt.getDimZ());
		popMatrix();
	}

	public void drawGui() {
		noLights();
		hint(DISABLE_DEPTH_TEST);
		fill(10);
		stroke(100);
		rect(0, 0, 100, height);
		gui.display(g);
	}

	// returns a string encoding the current time
	public String timeStamp() {
		return year() + nf(month(), 2) + nf(day(), 2) + "-" + nf(hour(), 2)
				+ nf(minute(), 2) + nf(second(), 2);
	}

	public void keyPressed() {
		if (key == 'u') {
			flagUpdateInsideOutside = true;
		}
		if (key == 'd') {
			displayMode++;
			displayMode = displayMode % 3;
		}
		if (key == 'e') {
			flagExport = true;
		}
		if (key == 'r') {
			rY -= PI / 4;
		}
	}
}
